/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "../commonheaders.h"
#include "fileXML.h"
#include "../rfcCodecs.h"


namespace NExImport {

/***********************************************************************************************************
 * common stuff
 ***********************************************************************************************************/

/**
 * name:	SortProc
 * desc:	used for bsearch in CExImContactXML::IsContactInfo
 * param:	item1	- item to compare
 *			item2	- item to compare
 * return:	the difference
 **/
static INT SortProc( const LPDWORD item1, const LPDWORD item2 )
{
	return *item1 - *item2;
}

/**
 * name:	CExImContactXML
 * class:	CExImContactXML
 * desc:	the constructor for the contact class
 * param:	pXmlFile	- the owning xml file
 * return:	nothing
 **/
CExImContactXML::CExImContactXML( CFileXml * pXmlFile )
	: CExImContactBase()
{
	_xmlNode = NULL;
	_pXmlFile = pXmlFile;
	_hEvent = NULL;
}

/**
 * name:	IsContactInfo
 * class:	CExImContactXML
 * desc:	this function compares the given setting key to the list of known contact
 *			information keys
 * param:	pszKey	- the settings key to check
 * return:	TRUE if pszKey is a valid contact information
 **/
BOOLEAN CExImContactXML::IsContactInfo( LPCSTR pszKey )
{
	// This is a sorted list of all hashvalues of the contact information.
	// This is the same as the szCiKey[] array below but sorted
	const DWORD dwCiHash[] = {
		0xE263B4C7,0xE2D4B620,0xE9338E02,0xEE6346F3,0xF6A29A30,0xFA011CE3,0x00019881,
		0x0001CD5A,0x0001EC46,0x003D5D43,0x031BC050,0x032BC050,0x033BC050,0x05333AB9,
		0x0562047D,0x0562057D,0x0562067D,0x0666B170,0x0667DD54,0x066B8AF3,0x077B3021,
		0x0D763751,0x1AAD7D7F,0x1B38C893,0x1CB6B229,0x213AFB93,0x3183AE45,0x32B3D35D,
		0x369B0C15,0x369B0E15,0x369B0F15,0x4CDBC44C,0x4EDACEC0,0x59933A67,0x5AA25979,
		0x5B9D88F1,0x611443C1,0x6F2037B3,0x6F2037D1,0x6F2037D3,0x7089EC12,0x7089EE12,
		0x7089EF12,0x819F7570,0x84DBC37F,0x8C409E19,0x8D409E19,0x8DC09E19,0x8EBBC692,
		0x9A26DBAD,0x9A27D34D,0x9B782336,0x9BC35385,0x9EA131C9,0x9F67BF4B,0xA376D99D,
		0xA91C321D,0xA975EE05,0xAB1BB789,0xAB2BB789,0xAB3BB789,0xB27F9C48,0xB39CDB4B,
		0xB3B06C55,0xB4BBB453,0xCC4320D4,0xCD4320D4,0xCDC320D4,0xCE2913C9,0xDEA131C5,
		0xDEA131C9
	};
	if( pszKey && *pszKey ) {
		char buf[MAXSETTING];
		// convert to hash and make bsearch as it is much faster then working with strings
		const DWORD dwHash = hashSetting( _strlwr( mir_strncpy( buf, pszKey, SIZEOF( buf ) ) ) );
		return bsearch( &dwHash, dwCiHash, SIZEOF( dwCiHash ), sizeof( dwCiHash[0] ), ( INT ( * )( const VOID*, const VOID* ) )SortProc ) != NULL;
	}
	return FALSE;
/*
	WORD i;
	const LPCSTR szCiKey[] = {
		// naming
		SET_CONTACT_TITLE,SET_CONTACT_FIRSTNAME,SET_CONTACT_SECONDNAME,SET_CONTACT_LASTNAME,SET_CONTACT_PREFIX,
		// private address
		SET_CONTACT_STREET,SET_CONTACT_ZIP,SET_CONTACT_CITY,SET_CONTACT_STATE,SET_CONTACT_COUNTRY,
		SET_CONTACT_PHONE,SET_CONTACT_FAX,SET_CONTACT_CELLULAR,
		SET_CONTACT_EMAIL,SET_CONTACT_EMAIL0,SET_CONTACT_EMAIL1,SET_CONTACT_HOMEPAGE,
		// origin
		SET_CONTACT_ORIGIN_STREET,SET_CONTACT_ORIGIN_ZIP,SET_CONTACT_ORIGIN_CITY,SET_CONTACT_ORIGIN_STATE,SET_CONTACT_ORIGIN_COUNTRY,
		// company
		SET_CONTACT_COMPANY_POSITION,SET_CONTACT_COMPANY_OCCUPATION,SET_CONTACT_COMPANY_SUPERIOR,SET_CONTACT_COMPANY_ASSISTENT
		SET_CONTACT_COMPANY,SET_CONTACT_COMPANY_DEPARTMENT,SET_CONTACT_COMPANY_OFFICE,
		SET_CONTACT_COMPANY_STREET,SET_CONTACT_COMPANY_ZIP,SET_CONTACT_COMPANY_CITY,SET_CONTACT_COMPANY_STATE,SET_CONTACT_COMPANY_COUNTRY,
		SET_CONTACT_COMPANY_PHONE,SET_CONTACT_COMPANY_FAX,SET_CONTACT_COMPANY_CELLULAR,
		SET_CONTACT_COMPANY_EMAIL,SET_CONTACT_COMPANY_EMAIL0,SET_CONTACT_COMPANY_EMAIL1,SET_CONTACT_COMPANY_HOMEPAGE,
		// personal information
		SET_CONTACT_ABOUT,SET_CONTACT_MYNOTES,SET_CONTACT_MARITAL,SET_CONTACT_PARTNER,
		SET_CONTACT_LANG1,SET_CONTACT_LANG2,SET_CONTACT_LANG3,SET_CONTACT_TIMEZONE,SET_CONTACT_AGE,SET_CONTACT_GENDER,
		SET_CONTACT_BIRTHDAY,SET_CONTACT_BIRTHMONTH,SET_CONTACT_BIRTHYEAR,
		"Past0", "Past0Text","Past1", "Past1Text","Past2", "Past2Text",
		"Affiliation0", "Affiliation0Text","Affiliation1", "Affiliation1Text","Affiliation2", "Affiliation2Text",
		"Interest0Cat", "Interest0Text","Interest1Cat", "Interest1Text","Interest2Cat", "Interest2Text"
	};
	DWORD *hash = new DWORD[SIZEOF( szCiKey )];
	char buf[MAX_PATH];

	for( i = 0; i < SIZEOF( szCiKey ); i++ ) {
		strcpy( buf, szCiKey[i] );
		hash[i] = hashSetting( _strlwr( ( char* )buf ) );
	}
	qsort( hash, SIZEOF( szCiKey ), sizeof( hash[0] ), 
	( INT ( * )( const VOID*, const VOID* ) )SortProc );
	
	FILE* fil = fopen( "D:\\temp\\id.txt", "wt" );
	for( i = 0; i < SIZEOF( szCiKey ); i++ ) {
		fprintf( fil, "0x%08X,", hash[i] );
	}
	fclose( fil );
	return FALSE;
	*/
}

/***********************************************************************************************************
 * exporting stuff
 ***********************************************************************************************************/

/**
 * name:	CreateXmlNode
 * class:	CExImContactXML
 * desc:	creates a new TiXmlElement representing the contact
 *			whose information are stored in this class
 * param:	none
 * return:	pointer to the newly created TiXmlElement
 **/
TiXmlElement* CExImContactXML::CreateXmlElement()
{
	if( _hContact ) {
		if( proto() ) {
			_xmlNode = new TiXmlElement( XKEY_CONTACT );
			
			if( _xmlNode ) {
				LPSTR pszUID = uid2String( TRUE );
				_xmlNode->SetAttribute( "ampro", ampro() );
				_xmlNode->SetAttribute( "proto", proto() );

				if( disp() ) _xmlNode->SetAttribute( "disp", disp() );
				if( nick() ) _xmlNode->SetAttribute( "nick", nick() );
				if( group() ) _xmlNode->SetAttribute( "group", group() );
				
				if( pszUID ) {

					if( uidk() ) {
						_xmlNode->SetAttribute( "uidk", uidk() );
						_xmlNode->SetAttribute( "uidv", pszUID );
					}
					else {
						_xmlNode->SetAttribute( "uidk", "#NV" );
						_xmlNode->SetAttribute( "uidv", "UNLOADED" );
					}
					mir_free( pszUID );
				}
			}
		}
		else
			_xmlNode = NULL;
	}
	else {
		_xmlNode = new TiXmlElement( XKEY_OWNER );
	}
	return _xmlNode;
}

/**
 * name:	ExportContact
 * class:	CExImContactXML
 * desc:	exports a contact
 * param:	none
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportContact( DB::CEnumList* pModules )
{
	if( _pXmlFile->_wExport & EXPORT_DATA ) 
	{
		if( pModules ) 
		{
			INT i;
			LPSTR p;

			for( i = 0; i < pModules->getCount(); i++ )
			{
				p = ( *pModules )[i];

				/*Filter/
				if( mir_stricmp( p, "Protocol" ) && !DB::Module::IsMeta( p ) )*/
				{
					ExportModule( p );
				}
			}
		}
		else 
		{
			ExportModule( USERINFO );
			ExportModule( MOD_MBIRTHDAY );
		}
	}
	
	// export contact's events
	if( _pXmlFile->_wExport & EXPORT_HISTORY )
	{
		ExportEvents();
	}

	return ERROR_OK;
}

/**
 * name:	ExportSubContact
 * class:	CExImContactXML
 * desc:	exports a meta sub contact
 * param:	none
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportSubContact( CExImContactXML *vMetaContact, DB::CEnumList* pModules )
{
	// create xmlNode
	if( !CreateXmlElement() ) 
	{
		return ERROR_INVALID_CONTACT;
	}
	if( ExportContact( pModules ) == ERROR_OK ) 
	{
		if( !_xmlNode->NoChildren() && vMetaContact->_xmlNode->LinkEndChild( _xmlNode ) ) 
		{
			return ERROR_OK;
		}
	}
	if( _xmlNode ) delete _xmlNode;
	return ERROR_NOT_ADDED;
}

/**
 * name:	Export
 * class:	CExImContactXML
 * desc:	exports a contact
 * param:	xmlfile		- handle to the open file to write the contact to
 *			pModules	- list of modules to export for each contact
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::Export( FILE *xmlfile, DB::CEnumList* pModules )
{
	if( !xmlfile ) 
	{
		return ERROR_INVALID_PARAMS;
	}

	if( _hContact == INVALID_HANDLE_VALUE )
	{
		return ERROR_INVALID_CONTACT;
	}

	if( !CreateXmlElement() ) 
	{
		return ERROR_INVALID_CONTACT;
	}

	// export meta
	if( isMeta() )
	{
		CExImContactXML vContact( _pXmlFile );

		const INT cnt = DB::MetaContact::SubCount( _hContact );
		const INT def = DB::MetaContact::SubDefNum( _hContact );
		HANDLE hSubContact = ( HANDLE )DB::MetaContact::Sub( _hContact, def );
		INT i;

		// export default subcontact
		if( hSubContact && vContact.fromDB( hSubContact ) )
		{
			vContact.ExportSubContact( this, pModules );
		}

		for( i = 0; i < cnt; i++ )
		{
			if( i != def )
			{
				hSubContact = DB::MetaContact::Sub( _hContact, i );
				if( hSubContact && vContact.fromDB( hSubContact ) )
				{
					vContact.ExportSubContact( this, pModules );
				}
			}
		}
	}
	ExportContact( pModules );

	// add xContact to document
	if( _xmlNode->NoChildren() )
	{
		delete _xmlNode;
		_xmlNode = NULL;
		return ERROR_NOT_ADDED;
	}
	_xmlNode->Print( xmlfile, 1 );
	fputc( '\n', xmlfile );

	delete _xmlNode;
	_xmlNode = NULL;

	return ERROR_OK;
}

/**
 * name:	ExportModule
 * class:	CExImContactXML
 * desc:	enumerates all settings of a database module and adds them to the xml tree
 * params:	szModule	- the module which is to export
 * return:	ERROR_OK on success or any other on failure
 **/
INT CExImContactXML::ExportModule( LPCSTR szModule )
{
	DB::CEnumList	Settings;

	if( !szModule || !*szModule )
	{
		return ERROR_INVALID_PARAMS;
	}
	if( !Settings.EnumSettings( _hContact, szModule ) )
	{
		INT i;
		TiXmlElement *xmod;
		
		xmod = new TiXmlElement( XKEY_MOD );
		if( !xmod )
		{
			return ERROR_MEMORY_ALLOC;
		}
		xmod->SetAttribute( "key", szModule );

		for( i = 0; i < Settings.getCount(); i++ )
		{
			ExportSetting( xmod, szModule, Settings[i] );
		}

		if( !xmod->NoChildren() && _xmlNode->LinkEndChild( xmod ) ) 
		{
			return ERROR_OK;
		}
		delete xmod;
	}
	return ERROR_EMPTY_MODULE;
}

/**
 * name:	ExportSetting
 * desc:	read a setting from database and add an xmlelement to contact node
 * params:	xmlModule	- xml node to add the setting to
 *			hContact	- handle of the contact whose event chain is to export
 *			szModule	- the module which is to export
 *			szSetting	- the setting which is to export
 * return:	pointer to the added element
 **/
INT CExImContactXML::ExportSetting( TiXmlElement *xmlModule, LPCSTR szModule, LPCSTR szSetting )
{
	DBVARIANT dbv;
	TiXmlElement *xmlEntry = NULL;
	TiXmlText *xmlValue = NULL;
	CHAR buf[32];
	LPSTR str;

	//if( DB::Setting::GetUString( _hContact, szModule, szSetting, &dbv ) )
	if( DB::Setting::GetAsIs( _hContact, szModule, szSetting, &dbv ) )
		return ERROR_INVALID_VALUE;
	switch( dbv.type ) {
		case DBVT_BYTE:		//'b' bVal and cVal are valid
			buf[0] = 'b';
			_ultoa( dbv.bVal, buf + 1, 10 );
			xmlValue = new TiXmlText( buf );
			break;
		case DBVT_WORD:		//'w' wVal and sVal are valid
			buf[0] = 'w';
			_ultoa( dbv.wVal, buf + 1, 10 );
			xmlValue = new TiXmlText( buf );
			break;
		case DBVT_DWORD:	//'d' dVal and lVal are valid
			buf[0] = 'd';
			_ultoa( dbv.dVal, buf + 1, 10 );
			xmlValue = new TiXmlText( buf );
			break;
		case DBVT_ASCIIZ:	//'s' pszVal is valid
		{
			DB::Variant::ConvertString( &dbv, DBVT_UTF8 );
			DWORD len = mir_strlen( dbv.pszVal );
			if( len > 0 && ( str = ( LPSTR )mir_alloc( len + 2 ) ) ) {
				str[0] = 's';
				mir_strcpy( &str[1], dbv.pszVal );
				xmlValue = new TiXmlText( str );
				mir_free( str );
			}
			break;
		}
		case DBVT_WCHAR:	//'u' pszVal is valid
		case DBVT_UTF8:		//'u' pszVal is valid
		{
			DWORD len = mir_strlen( dbv.pszVal );
			if( len > 0 && ( str = ( LPSTR )mir_alloc( len + 2 ) ) ) {
				str[0] = 'u';
				mir_strcpy( &str[1], dbv.pszVal );
				xmlValue = new TiXmlText( str );
				mir_free( str );
			}
			break;
		}
		case DBVT_BLOB:		//'n' cpbVal and pbVal are valid
		{
			// new buffer for base64 encoded data
			INT baselen = Base64EncodeGetRequiredLength( dbv.cpbVal, BASE64_FLAG_NOCRLF );
			str = (LPSTR)mir_alloc(baselen + 6);
			assert(str != NULL);
			// encode data
			if( Base64Encode( dbv.pbVal, dbv.cpbVal, str+1, &baselen, BASE64_FLAG_NOCRLF ) ){
				if (baselen){
					str[baselen+1] = 0;
					str[0] = 'n';
					xmlValue = new TiXmlText( str );
				}
			}
			mir_free(str);
			break;
		}
		case DBVT_DELETED:	//this setting just got deleted, no other values are valid
			#if defined( _DEBUG )
				OutputDebugStringA( "DBVT_DELETED\n" );
			#endif
			break;
		default:
			#if defined( _DEBUG )
				OutputDebugStringA( "DBVT_TYPE unknown\n" );
			#endif
			; // nothing
	}
	DB::Variant::Free( &dbv );
	if( xmlValue ) {
		xmlEntry = new TiXmlElement( XKEY_SET );
		if( xmlEntry ) {
			xmlEntry->SetAttribute( "key", szSetting );
			if( xmlEntry->LinkEndChild( xmlValue ) && xmlModule->LinkEndChild( xmlEntry ) )
				return ERROR_OK;
			delete xmlEntry;
		}
		delete xmlValue;
	}
	return ERROR_MEMORY_ALLOC;
}

/**
 * name:	ExportEvents
 * desc:	adds the event chain for a given contact to the xml tree
 * params:	xContact	- the xml node to add the events as childs to
 *			hContact	- handle of the contact whose event chain is to export
 * return:	TRUE on success, FALSE otherwise
 **/
BOOLEAN CExImContactXML::ExportEvents()
{
	DBEVENTINFO	dbei; 
	HANDLE		hDbEvent;
	PBYTE		pbEventBuf = NULL;
	DWORD		cbEventBuf = 0,
				dwNumEvents = 0,
				dwNumEventsAdded = 0;
	LPSTR		pBase64Data = NULL;
	INT			cbBase64Data = 0,
				cbNewBase64Data = 0;

	TiXmlNode	 *xmlModule = NULL;
	TiXmlElement *xmlEvent = NULL;
	TiXmlText	 *xmlText = NULL;

	dwNumEvents = CallService( MS_DB_EVENT_GETCOUNT, ( WPARAM )_hContact, NULL );
	if( dwNumEvents == 0 ) return FALSE;

	try 
	{
		ZeroMemory( &dbei, sizeof( DBEVENTINFO ) );
		dbei.cbSize = sizeof( DBEVENTINFO );
	
		// read out all events for the current contact
		for( hDbEvent = DB::Event::FindFirst( _hContact ); hDbEvent != NULL; hDbEvent = DB::Event::FindNext( hDbEvent ) )
		{
			if( !DB::Event::GetInfoWithData( hDbEvent, &dbei ) )
			{
				// new buffer for base64 encoded data
				cbNewBase64Data = Base64EncodeGetRequiredLength( dbei.cbBlob, BASE64_FLAG_NOCRLF );
				if( cbNewBase64Data > cbBase64Data ) {
					if( pBase64Data ) mir_free( pBase64Data );
					pBase64Data = ( LPSTR )mir_alloc( cbNewBase64Data + 5 );
					if( pBase64Data == NULL ) {
						MessageBoxA( NULL, "mir_alloc( cbNewBase64Data + 5 ) == NULL", "Error", 0 );
						break;
					}
					cbBase64Data = cbNewBase64Data;
				}
				
				// encode data
				if( Base64Encode( dbei.pBlob, dbei.cbBlob, pBase64Data, &cbNewBase64Data, BASE64_FLAG_NOCRLF ) ) {
					pBase64Data[cbNewBase64Data] = 0;

				
					xmlEvent = new TiXmlElement( "evt" );
					if( xmlEvent ) {
						xmlEvent->SetAttribute( "type", dbei.eventType );
						xmlEvent->SetAttribute( "time", dbei.timestamp );
						xmlEvent->SetAttribute( "flag", dbei.flags );

						xmlText = new TiXmlText( pBase64Data );
						xmlEvent->LinkEndChild( xmlText );

						// find module
						for( xmlModule = _xmlNode->FirstChild(); xmlModule != NULL; xmlModule = xmlModule->NextSibling() ) {
							if( !mir_stricmp( (( TiXmlElement* )xmlModule )->Attribute( "key" ), dbei.szModule ) ) break;
						}
						// create new module
						if( !xmlModule ) {
							xmlModule = _xmlNode->InsertEndChild( TiXmlElement( XKEY_MOD ) );
							if( !xmlModule ) break;
							( ( TiXmlElement* )xmlModule )->SetAttribute( "key", dbei.szModule );
						}

						xmlModule->LinkEndChild( xmlEvent );
						dwNumEventsAdded++;
						xmlEvent = NULL; // avoid final deleting
					}
				}
				mir_free( dbei.pBlob );
			}
		}
	}
	catch( ... ) {
		// fuck, do nothing
		dwNumEventsAdded = 0;
	}

	if( pbEventBuf ) mir_free( pbEventBuf );
	if( pBase64Data ) mir_free( pBase64Data );
	if( xmlEvent ) delete xmlEvent;

	return dwNumEventsAdded == dwNumEvents;
}

/***********************************************************************************************************
 * importing stuff
 ***********************************************************************************************************/

/**
 * name:	CountKeys
 * desc:	Counts the number of events and settings stored for a contact
 * params:	xmlContact	- the contact, who is the owner of the keys to count
 * return:	nothing
 **/
VOID CExImContactXML::CountKeys( DWORD &numSettings, DWORD &numEvents )
{
	TiXmlNode *xmod, *xkey;

	numSettings = numEvents = 0;
	for( xmod = _xmlNode->FirstChild(); 
		xmod != NULL; 
		xmod = xmod->NextSibling( XKEY_MOD )
	 ) {
		for( xkey = xmod->FirstChild();
			xkey != NULL;
			xkey = xkey->NextSibling()
		 ) {
			if( !mir_stricmp( xkey->Value(), XKEY_SET ) ) numSettings++;
			else numEvents++;
		}
	}
}

/**
 * name:	LoadXmlElemnt
 * class:	CExImContactXML
 * desc:	get contact information from XML-file
 * param:	xContact	- TiXmlElement representing a contact
 * return:	ERROR_OK if successful or any other error number otherwise
 **/
INT CExImContactXML::LoadXmlElemnt( TiXmlElement *xContact )
{
	if( xContact == NULL ) return ERROR_INVALID_PARAMS;

	// delete last contact
	DB::Variant::Free( &_dbvUID );
	_hContact = INVALID_HANDLE_VALUE;

	_xmlNode = xContact;
	ampro( xContact->Attribute( "ampro") );
	nick( xContact->Attribute( "nick") );
	disp( xContact->Attribute( "disp") );
	group( xContact->Attribute( "group") );

	// is contact a metacontact
	if( _xmlNode->FirstChildElement( XKEY_CONTACT ) ) {
		TiXmlElement *xSub;

		proto( myGlobals.szMetaProto );

		// meta contact must be uniquelly identified by its subcontacts
		// the metaID may change during an export or import call
		for( xSub = xContact->FirstChildElement( XKEY_CONTACT ); xSub != NULL; xSub = xSub->NextSiblingElement( XKEY_CONTACT ) ) {
			CExImContactXML vSub( _pXmlFile );
			if( vSub = xSub ) {
				// identify metacontact by the first valid subcontact in xmlfile
				if( _hContact == INVALID_HANDLE_VALUE && vSub.handle() != INVALID_HANDLE_VALUE ) {
					HANDLE hMeta = ( HANDLE )CallService( MS_MC_GETMETACONTACT, ( WPARAM )vSub.handle(), NULL );
					if( hMeta != NULL ) {
						_hContact = hMeta;
						break;
					}
				}	
			}
		}
		// if no handle was found, this is a new meta contact
		_isNewContact = _hContact == INVALID_HANDLE_VALUE;
	}
	// entry is a default contact
	else {
		proto( xContact->Attribute( "proto") );
		uidk( xContact->Attribute( "uidk") );
		if( !proto() ) {
			// check if this is the owner contact
			if( mir_stricmp( xContact->Value(), XKEY_OWNER ) )
				return ERROR_INVALID_PARAMS;
			_hContact = NULL;
			_xmlNode = xContact;
			return ERROR_OK;
		}

		if( uidk() && mir_strcmp("#NV", uidk()) !=0 ) {
			LPCSTR pUID = xContact->Attribute( "uidv" );

			if( pUID != NULL ) {
				INT len = 0;
				DWORD baselen = NULL;
				PBYTE pbVal = NULL;

				switch( *( pUID++ ) ) {
					case 'b':
						uid( ( BYTE )atoi( pUID ) );
						break;
					case 'w':
						uid( ( WORD )atoi( pUID ) );
						break;
					case 'd':
						uid( ( DWORD )atoi( pUID ) );
						break;
					case 's':
						// utf8 -> asci
						uida( (LPCSTR) mir_utf8decodeA(( pUID )) );
						break;
					case 'u':
						uidu( pUID );
						break;
					case 'n':
						len = strlen( pUID );
						baselen = Base64DecodeGetRequiredLength( len );
						pbVal = ( PBYTE )mir_alloc( baselen /*+1*/ );
						if( pbVal != NULL ){
							if( Base64Decode( pUID, len, pbVal, ( LPINT )&baselen ) ){
								uidn( pbVal, baselen );
							}
							else {
								assert(pUID != NULL);
							}
						}
						break;
					default:
						uidu( ( LPCSTR )NULL );
						break;
				}
			}
		}
		// finally try to find contact in contact list
		findHandle();
	}
	return ERROR_OK;
}

/**
 * name:	ImportContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database
 * param:	none
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::ImportContact()
{
	TiXmlNode *xmod;

	// create the contact if not yet exists
	if( toDB() != INVALID_HANDLE_VALUE ) {
		DWORD numSettings, numEvents;

		_hEvent = NULL;

		// count settings and events and init progress dialog
		CountKeys( numSettings, numEvents );
		_pXmlFile->_progress.SetSettingsCount( numSettings + numEvents );
		_pXmlFile->_numSettingsTodo += numSettings;
		_pXmlFile->_numEventsTodo += numEvents;

		// import all modules
		for( xmod = _xmlNode->FirstChild(); xmod != NULL; xmod = xmod->NextSibling( XKEY_MOD ) ) {

			// import module
			if( ImportModule( xmod ) == ERROR_ABORTED ) {
				// ask to delete new incomplete contact
				if( _isNewContact && _hContact != NULL ) {
					INT result = MsgBox( NULL, MB_YESNO|MB_ICONWARNING, 
						LPGENT( "Question" ), 
						LPGENT( "Importing a new contact was aborted!" ), 
						LPGENT( "You aborted import of a new contact.\nSome information may be missing for this contact.\n\nDo you want to delete the incomplete contact?") );
					if( result == IDYES ) {
						CallService( MS_DB_CONTACT_DELETE, ( WPARAM )_hContact, NULL );
						_hContact = INVALID_HANDLE_VALUE;
					}
				}
				return ERROR_ABORTED;
			}
		}
		return ERROR_OK;
	}
	return ERROR_NOT_ADDED;
}

/**
 * name:	ImportNormalContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Remove contact from a metacontact if it is a subcontact
 * param:	none
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::ImportNormalContact()
{
	INT err = ImportContact();

	// remove contact from a metacontact
	if( err == ERROR_OK && CallService( MS_MC_GETMETACONTACT, ( WPARAM )_hContact, NULL ) ) {
		CallService( MS_MC_REMOVEFROMMETA, NULL, ( LPARAM )_hContact );
	}	
	return err;
}

/**
 * name:	Import
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Remove contact from a metacontact if it is a subcontact
 * param:	TRUE = keepMetaSubContact
 * return:	ERROR_OK on success or any other error number otherwise
 **/
INT CExImContactXML::Import( BOOLEAN keepMetaSubContact )
{
	TiXmlElement *xContact = _xmlNode->FirstChildElement( "CONTACT" );
	INT result;

	// xml contact contains subcontacts?
	if( xContact ) {

		// contact is a metacontact and metacontacts plugin is installed?
		if( isMeta() ) {
			// create object for first sub contact
			CExImContactXML	vContact( _pXmlFile );
			LPTSTR pszNick;

			// the contact does not yet exist
			if( _isNewContact ) {
				// import default contact as normal contact and convert to meta contact
				if( !( vContact = xContact ) ) {
					return ERROR_CONVERT_METACONTACT;
				}
				// import as normal contact
				result = vContact.ImportContact();
				if( result != ERROR_OK ) return result;
				// convert default subcontact to metacontact
				_hContact = ( HANDLE )CallService( MS_MC_CONVERTTOMETA, ( WPARAM )vContact.handle(), NULL );
				if( _hContact == NULL ) {
					_hContact = INVALID_HANDLE_VALUE;
					return ERROR_CONVERT_METACONTACT;
				}
				_pXmlFile->_numContactsDone++;
				// do not load first meta contact twice
				xContact = xContact->NextSiblingElement( "CONTACT" );
			}

			// load contact information
			ImportContact();

			// load all subcontacts
			do {
				// update progressbar and abort if user clicked cancel
				pszNick = mir_utf8decodeT( xContact->Attribute( "nick") );
				result = _pXmlFile->_progress.UpdateContact( LPGENT( "Sub Contact: %s ( %S )" ), pszNick, xContact->Attribute( "proto") );
				if( pszNick ) mir_free( pszNick );
				// user clicked abort button
				if( !result ) break;

				if( vContact = xContact ) {
					if( vContact.ImportMetaSubContact( this ) == ERROR_ABORTED )
						return ERROR_ABORTED;
					_pXmlFile->_numContactsDone++;
				}
			}
			while( xContact = xContact->NextSiblingElement( "CONTACT") );
			return ERROR_OK;
		}
		// import sub contacts as normal contacts
		return _pXmlFile->ImportContacts( _xmlNode );
	}

	// load contact information
	result = ImportContact();
	if( result == ERROR_OK && !keepMetaSubContact ) {
		CallService( MS_MC_REMOVEFROMMETA, NULL, ( LPARAM )_hContact );
	}

	return result;
}

/**
 * name:	ImportMetaSubContact
 * class:	CExImContactXML
 * desc:	create the contact if neccessary and copy
 *			all information from the xmlNode to database.
 *			Add this contact to an meta contact
 * param:	pMetaContact	- the meta contact to add this one to
 * return:	
 **/
INT CExImContactXML::ImportMetaSubContact( CExImContactXML * pMetaContact )
{
	INT err = ImportContact();

	// abort here if contact was not imported correctly
	if( err != ERROR_OK ) return err;

	// check if contact is subcontact of the desired meta contact
	if( ( HANDLE )CallService( MS_MC_GETMETACONTACT, ( WPARAM )_hContact, NULL ) != pMetaContact->handle() ) {
		// add contact to the metacontact ( this service returns TRUE if successful )	
		err = CallService( MS_MC_ADDTOMETA, ( WPARAM )_hContact, ( LPARAM )pMetaContact->handle() );
		if( err == FALSE ) {
			// ask to delete new contact
			if( _isNewContact && _hContact != NULL ) {
				LPTSTR ptszNick = mir_utf8decodeT( nick() );
				LPTSTR ptszMetaNick = mir_utf8decodeT( pMetaContact->nick() );
				INT result = MsgBox( NULL, MB_YESNO|MB_ICONWARNING, 
					LPGENT( "Question" ), 
					LPGENT( "Importing a new meta subcontact failed!" ), 
					LPGENT( "The newly created MetaSubContact '%s'\ncould not be added to MetaContact '%s'!\n\nDo you want to delete this contact?" ),
					ptszNick, ptszMetaNick );
				if( ptszNick ) mir_free( ptszNick );
				if( ptszMetaNick ) mir_free( ptszMetaNick );
				if( result == IDYES ) {
					CallService( MS_DB_CONTACT_DELETE, ( WPARAM )_hContact, NULL );
					_hContact = INVALID_HANDLE_VALUE;
				}
			}
			return ERROR_ADDTO_METACONTACT;
		}
	}
	return ERROR_OK;
}

/**
 * name:	ImportModule
 * class:	CExImContactXML
 * desc:	interprete an xmlnode as module and add the children to database.
 * params:	hContact	- handle to the contact, who is the owner of the setting to import
 *			xmlModule	- xmlnode representing the module
 *			stat		- structure used to collect some statistics
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportModule( TiXmlNode* xmlModule )
{
	TiXmlElement *xMod;
	TiXmlElement *xKey;
	LPCSTR szModule;
	BOOLEAN isProtoModule;
	BOOLEAN isMetaModule;
	
	// check if parent is really a module
	if( !xmlModule || mir_stricmp( xmlModule->Value(), XKEY_MOD ) )
		return ERROR_INVALID_SIGNATURE;
	// convert to element
	if( !( xMod = xmlModule->ToElement() ) )
		return ERROR_INVALID_PARAMS;
	// get module name
	szModule = xMod->Attribute( "key" );
	if( !szModule || !*szModule )
		return ERROR_INVALID_PARAMS;
	// ignore Modul 'Protocol' as it would cause trouble
	if( !mir_stricmp( szModule, "Protocol") )
		return ERROR_OK;
	
	// check if the module to import is the contact's protocol module
	isProtoModule = !mir_stricmp( szModule, proto() )/* || DB::Module::IsMeta( szModule )*/;
	isMetaModule = DB::Module::IsMeta( szModule );

	for( xKey = xmlModule->FirstChildElement(); xKey != NULL; xKey = xKey->NextSiblingElement() ) {
		// import setting
		if( !mir_stricmp( xKey->Value(), XKEY_SET ) ) {
			// just ignore MetaModule to avoid errors (only keys)
			if ( isMetaModule ) {
				continue;
			}
			// just ignore some settings of protocol module to avoid errors (only keys)
			if ( isProtoModule && !isMetaModule) {
				if (!IsContactInfo( xKey->Attribute( "key") ) ) {
					if (ImportSetting( szModule, xKey->ToElement() ) == ERROR_OK ) {
						_pXmlFile->_numSettingsDone++;
					}
				}
			}
			// other module
			else if (ImportSetting( szModule, xKey->ToElement() ) == ERROR_OK ) {
					_pXmlFile->_numSettingsDone++;
			}
			if( !_pXmlFile->_progress.UpdateSetting( LPGENT( "Settings: %S" ), szModule ) )
				return ERROR_ABORTED;
		}
		// import event
		else if ( !mir_stricmp( xKey->Value(), XKEY_EVT ) ) {
			INT error = ImportEvent( szModule, xKey->ToElement() );
			switch( error ) {
				case ERROR_OK:
					_pXmlFile->_numEventsDone++;
					break;
				case ERROR_DUPLICATED:
					_pXmlFile->_numEventsDuplicated++;
					break;
			}
			if( !_pXmlFile->_progress.UpdateSetting( LPGENT( "Events: %S" ), szModule ) )
				return ERROR_ABORTED;
		}
	}
	return ERROR_OK;
}

/**
 * name:	ImportSetting
 * class:	CExImContactXML
 * desc:	interprete an setting representing xmlnode and write the corresponding setting to database.
 * params:	xmlModule	- xmlnode representing the module to write the setting to in the database
 *			xmlEntry	- xmlnode representing the setting to import
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportSetting( LPCSTR szModule, TiXmlElement *xmlEntry )
{
	DBCONTACTWRITESETTING cws = {0};
	TiXmlText* xval;
	LPCSTR value;
	
	// validate parameter
	if( !xmlEntry || !szModule || !*szModule )
		return ERROR_INVALID_PARAMS;

	// validate value
	xval = ( TiXmlText* )xmlEntry->FirstChild();
	if( !xval || xval->Type() != TiXmlText::TEXT )
		return ERROR_INVALID_VALUE;
	value = xval->Value();

	// init write structure
	cws.szModule = ( LPSTR )szModule;
	cws.szSetting = xmlEntry->Attribute( "key" );
	
	// convert data
	INT len = 0;
	DWORD baselen = NULL;

switch( value[0] ) {
		case 'b':			//'b' bVal and cVal are valid
			cws.value.type = DBVT_BYTE;
			cws.value.bVal = ( BYTE )atoi( value + 1 );
			break;
		case 'w':			//'w' wVal and sVal are valid
			cws.value.type = DBVT_WORD;
			cws.value.wVal = ( WORD )atoi( value + 1 );
			break;
		case 'd':			//'d' dVal and lVal are valid
			cws.value.type = DBVT_DWORD;
			//cws.value.dVal = ( DWORD )_atoi64( value + 1 );
			cws.value.dVal = ( DWORD )atoi( value + 1 );
			break;
		case 's':			//'s' pszVal is valid
			cws.value.type = DBVT_ASCIIZ;
			cws.value.pszVal = ( LPSTR ) mir_utf8decodeA(( value + 1 ));
			break;
		case 'u':
			cws.value.type = DBVT_UTF8;
			cws.value.pszVal = ( LPSTR )( value + 1 );
			break;
		case 'n':
			len = strlen( value + 1 );
			baselen = Base64DecodeGetRequiredLength( len );
			cws.value.type = DBVT_BLOB;
			cws.value.pbVal = ( PBYTE )mir_alloc( baselen +1 );
			if( cws.value.pbVal != NULL ){
				if( Base64Decode( ( value + 1 ), len, cws.value.pbVal, ( LPINT )&baselen ) ){
					cws.value.cpbVal = baselen;
				}
				else {
					mir_free( cws.value.pbVal );
					return ERROR_NOT_ADDED;
				}
			}
			break;
		default:
			return ERROR_INVALID_TYPE;
	}
	// write value to db
	if( CallService( MS_DB_CONTACT_WRITESETTING, ( WPARAM )_hContact, ( LPARAM )&cws ) ) {
		if (cws.value.pbVal>0) mir_free( cws.value.pbVal );
		return ERROR_NOT_ADDED;
	}
	if (cws.value.pbVal>0) mir_free( cws.value.pbVal );
	return ERROR_OK;
}

/**
 * name:	ImportEvent
 * class:	CExImContactXML
 * desc:	interprete an xmlnode and add the corresponding event to database.
 * params:	hContact	- handle to the contact, who is the owner of the setting to import
 *			xmlModule	- xmlnode representing the module to write the setting to in the database
 *			xmlEvent	- xmlnode representing the event to import
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CExImContactXML::ImportEvent( LPCSTR szModule, TiXmlElement *xmlEvent )
{
	DBEVENTINFO	dbei;
	TiXmlText* xmlValue;
	LPCSTR tmp;
	INT cbSrc;

	if( !xmlEvent || !szModule || !*szModule )
		return ERROR_INVALID_PARAMS;

	dbei.pBlob = NULL;
	dbei.cbBlob = NULL;

	if( stricmp( xmlEvent->Value(), "evt") )
		return ERROR_NOT_ADDED;

	// timestamp must be valid
	xmlEvent->Attribute( "time", ( LPINT )&dbei.timestamp );
	if( dbei.timestamp == 0 ) return ERROR_INVALID_TIMESTAMP;

	xmlValue = ( TiXmlText* )xmlEvent->FirstChild();
	if( !xmlValue || xmlValue->Type() != TiXmlText::TEXT )
		return ERROR_INVALID_VALUE;
	tmp = xmlValue->Value();
	if( !tmp || tmp[0] == 0 )
		return ERROR_INVALID_VALUE;

	cbSrc = strlen( tmp );
	dbei.cbBlob = Base64DecodeGetRequiredLength( cbSrc );
	dbei.pBlob = ( PBYTE )mir_alloc( dbei.cbBlob + 1 );
	if( dbei.pBlob != NULL ) {
		if( Base64Decode( tmp, cbSrc, dbei.pBlob, ( LPINT )&dbei.cbBlob ) ) {
			INT hEvent;

			// event owning module
			dbei.cbSize = sizeof( dbei );
			dbei.szModule = ( LPSTR )szModule;

			xmlEvent->Attribute( "type", ( LPINT )&dbei.eventType );
			xmlEvent->Attribute( "flag", ( LPINT )&dbei.flags );
			if( dbei.flags == 0 ) dbei.flags = DBEF_READ;

			// search for existing event to avoid duplicates if contact exists in database
			if( !_isNewContact && DB::Event::Exists( _hContact, _hEvent, &dbei ) ) {
				mir_free( dbei.pBlob );
				return ERROR_DUPLICATED;
			}

			hEvent = CallService( MS_DB_EVENT_ADD, ( WPARAM )_hContact, ( LPARAM )&dbei );
			mir_free( dbei.pBlob );
			if( hEvent ) {
				_hEvent = ( HANDLE )hEvent;
				return ERROR_OK;
			}
		}
	}
	return ERROR_NOT_ADDED;
}

} // namespace NExImport